
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>canvas扩散</title>


<style>
*{margin:0;padding:0;}
html, body {
    overflow: hidden;
    width: 100%;
    height: 100%;
    cursor: pointer;
}
</style>

</head>
<body>

<canvas id="canvas"></canvas>

<script>
var ClassicalNoise = function(r) { // Classic Perlin noise in 3D, for comparison 
	if (r == undefined) r = Math;
  this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], 
                                 [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], 
                                 [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; 
  this.p = [];
  for (var i=0; i<256; i++) {
	  this.p[i] = Math.floor(r.random()*256);
  }
  // To remove the need for index wrapping, double the permutation table length 
  this.perm = []; 
  for(var i=0; i<512; i++) {
		this.perm[i]=this.p[i & 255];
  }
};

ClassicalNoise.prototype.dot = function(g, x, y, z) { 
    return g[0]*x + g[1]*y + g[2]*z; 
};

ClassicalNoise.prototype.mix = function(a, b, t) { 
    return (1.0-t)*a + t*b; 
};

ClassicalNoise.prototype.fade = function(t) { 
    return t*t*t*(t*(t*6.0-15.0)+10.0); 
};

  // Classic Perlin noise, 3D version 
ClassicalNoise.prototype.noise = function(x, y, z) { 
  // Find unit grid cell containing point 
  var X = Math.floor(x); 
  var Y = Math.floor(y); 
  var Z = Math.floor(z); 
  
  // Get relative xyz coordinates of point within that cell 
  x = x - X; 
  y = y - Y; 
  z = z - Z; 
  
  // Wrap the integer cells at 255 (smaller integer period can be introduced here) 
  X = X & 255; 
  Y = Y & 255; 
  Z = Z & 255;
  
  // Calculate a set of eight hashed gradient indices 
  var gi000 = this.perm[X+this.perm[Y+this.perm[Z]]] % 12; 
  var gi001 = this.perm[X+this.perm[Y+this.perm[Z+1]]] % 12; 
  var gi010 = this.perm[X+this.perm[Y+1+this.perm[Z]]] % 12; 
  var gi011 = this.perm[X+this.perm[Y+1+this.perm[Z+1]]] % 12; 
  var gi100 = this.perm[X+1+this.perm[Y+this.perm[Z]]] % 12; 
  var gi101 = this.perm[X+1+this.perm[Y+this.perm[Z+1]]] % 12; 
  var gi110 = this.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; 
  var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12; 
  
  // The gradients of each corner are now: 
  // g000 = grad3[gi000]; 
  // g001 = grad3[gi001]; 
  // g010 = grad3[gi010]; 
  // g011 = grad3[gi011]; 
  // g100 = grad3[gi100]; 
  // g101 = grad3[gi101]; 
  // g110 = grad3[gi110]; 
  // g111 = grad3[gi111]; 
  // Calculate noise contributions from each of the eight corners 
  var n000= this.dot(this.grad3[gi000], x, y, z); 
  var n100= this.dot(this.grad3[gi100], x-1, y, z); 
  var n010= this.dot(this.grad3[gi010], x, y-1, z); 
  var n110= this.dot(this.grad3[gi110], x-1, y-1, z); 
  var n001= this.dot(this.grad3[gi001], x, y, z-1); 
  var n101= this.dot(this.grad3[gi101], x-1, y, z-1); 
  var n011= this.dot(this.grad3[gi011], x, y-1, z-1); 
  var n111= this.dot(this.grad3[gi111], x-1, y-1, z-1); 
  // Compute the fade curve value for each of x, y, z 
  var u = this.fade(x); 
  var v = this.fade(y); 
  var w = this.fade(z); 
   // Interpolate along x the contributions from each of the corners 
  var nx00 = this.mix(n000, n100, u); 
  var nx01 = this.mix(n001, n101, u); 
  var nx10 = this.mix(n010, n110, u); 
  var nx11 = this.mix(n011, n111, u); 
  // Interpolate the four results along y 
  var nxy0 = this.mix(nx00, nx10, v); 
  var nxy1 = this.mix(nx01, nx11, v); 
  // Interpolate the two last results along z 
  var nxyz = this.mix(nxy0, nxy1, w); 

  return nxyz; 
};

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _class2, _temp, _initialiseProps;

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

//*??????????????????????????????/
// Utils
//*?????????????????????????????*/

function cycle(value, total) {
    return (value % total + total) % total;
}

function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
    return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}

function movePointAtAngle(point, angle, distance) {
    return {
        x: point.x + Math.cos(angle) * distance,
        y: point.y + Math.sin(angle) * distance
    };
}

//*??????????????????????????????/
// Element
//*?????????????????????????????*/

var Element = function Element() {
    var _this = this;

    _classCallCheck(this, Element);

    this.dpr = 1;

    this.toValue = function (value) {
        return value * _this.dpr;
    };

    this.draw = function () {};

    this.update = function () {};
};

//*??????????????????????????????/
// Wave
//*?????????????????????????????*/

var Wave = (_temp = _class2 = function (_Element) {
    _inherits(Wave, _Element);

    function Wave(pointAmount, p1, p2) {
        _classCallCheck(this, Wave);

        var _this2 = _possibleConstructorReturn(this, (Wave.__proto__ || Object.getPrototypeOf(Wave)).call(this));

        _initialiseProps.call(_this2);

        _this2.p1 = p1;
        _this2.p2 = p2;

        var dx = p2.x - p1.x;
        var dy = p2.y - p1.y;

        var vx = dx / (pointAmount - 1);
        var vy = dy / (pointAmount - 1);

        _this2.vertices = new Array(pointAmount).fill(null).map(function (p, i) {
            return {
                x: p1.x + vx * i,
                y: p1.y + vy * i
            };
        });
        return _this2;
    }

    return Wave;
}(Element), _initialiseProps = function _initialiseProps() {
    var _this3 = this;

    this.draw = function (_ref) {
        var ctx = _ref.ctx;

        ctx.lineCap = 'round';
        ctx.lineWidth = _this3.toValue(2);
        ctx.strokeStyle = '#ccc';

        ctx.beginPath();
        ctx.moveTo(_this3.vertices[0].x, _this3.vertices[0].y);

        for (var k = 0; k < _this3.vertices.length - 1; k++) {
            var p1 = _this3.vertices[k];
            var p2 = _this3.vertices[k + 1];

            var cpx = (p1.x + p2.x) / 2;
            var cpy = (p1.y + p2.y) / 2;

            // ctx.fillStyle = 'white';
            // ctx.fillRect(p1.x, p1.y, 10, 10);

            if (k === _this3.vertices.length - 2) {
                ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
            } else {
                ctx.quadraticCurveTo(p1.x, p1.y, cpx, cpy);
            }
        }

        ctx.stroke();
    };

    this.update = function (_ref2) {
        var tick = _ref2.tick;

        var l = _this3.vertices.length;
        var r = 1 / l;
        _this3.vertices = _this3.vertices.map(function (p, i) {
            return {
                x: p.x,
                y: p.y + _this3.toValue(Math.sin(tick / 10 + i))
            };
        });
    };
}, _temp);

//*??????????????????????????????/
// Gate
//*?????????????????????????????*/

var Gate = function (_Element2) {
    _inherits(Gate, _Element2);

    function Gate(_ref3) {
        var radius = _ref3.radius,
            amount = _ref3.amount;

        _classCallCheck(this, Gate);

        var _this4 = _possibleConstructorReturn(this, (Gate.__proto__ || Object.getPrototypeOf(Gate)).call(this));

        _this4.reset = function () {
            _this4.setValues();
            _this4.setupWaves();
        };

        _this4.draw = function (canvas) {
            _this4.drawBg(canvas);

            canvas.ctx.save();
            canvas.ctx.beginPath();
            canvas.ctx.arc(_this4.hw, _this4.hh, _this4.radius, 0, 2 * Math.PI);
            canvas.ctx.clip();

            _this4.waves.map(function (wave) {
                wave.draw(canvas);
                wave.update(canvas);
            });

            canvas.ctx.restore();
        };

        _this4.amount = amount;
        _this4.radius = _this4.toValue(radius);
        _this4.diameter = _this4.radius * 2;

        _this4.setValues();
        _this4.setupWaves();
        return _this4;
    }

    _createClass(Gate, [{
        key: 'setValues',
        value: function setValues() {
            this.w = this.toValue(window.innerWidth);
            this.h = this.toValue(window.innerHeight);
            this.hw = this.w / 2;
            this.hh = this.h / 2;
        }
    }, {
        key: 'setupWaves',
        value: function setupWaves() {
            // Gate dims
            this.px = this.hw - this.radius;
            this.py = this.hh - this.radius;
            this.pw = this.diameter;
            this.ph = this.diameter;
            this.waves = [];

            var extend = 2;

            for (var i = -extend; i < this.amount + extend; i++) {
                var dy = this.ph / this.amount * i;
                var p1 = {
                    x: this.px,
                    y: this.py + dy
                };
                var p2 = {
                    x: this.px + this.pw,
                    y: this.py + dy
                };
                this.waves.push(new Wave(10, p1, p2));
            }
        }
    }, {
        key: 'drawBg',
        value: function drawBg(canvas) {
            canvas.ctx.beginPath();

            var gradient = canvas.ctx.createLinearGradient(this.px, this.py, this.px, this.py + this.diameter);
            gradient.addColorStop(0, '#2a5298');
            gradient.addColorStop(1, '#4298b7');

            var wobble = this.toValue(Math.sin(canvas.tick / 20) * 10);
            var offset = this.toValue(5);

            canvas.ctx.arc(this.hw, this.hh, this.radius + offset + wobble, 0, 2 * Math.PI);
            canvas.ctx.fillStyle = gradient;
            canvas.ctx.fill();
        }
    }]);

    return Gate;
}(Element);

//*??????????????????????????????/
// Ring
//*?????????????????????????????*/

var Ring = function (_Element3) {
    _inherits(Ring, _Element3);

    function Ring(_ref4) {
        var radius = _ref4.radius,
            pointAmount = _ref4.pointAmount,
            speed = _ref4.speed,
            decay = _ref4.decay,
            acceleration = _ref4.acceleration,
            wobble = _ref4.wobble,
            warp = _ref4.warp;

        _classCallCheck(this, Ring);

        var _this5 = _possibleConstructorReturn(this, (Ring.__proto__ || Object.getPrototypeOf(Ring)).call(this));

        _this5.draw = function (_ref5) {
            var ctx = _ref5.ctx;
            var p = _this5.points,
                v = _this5.toValue;


            ctx.lineWidth = _this5.lineWidth;
            ctx.strokeStyle = _this5.stroke;
            ctx.beginPath();
            ctx.moveTo((p[cycle(-1, p.length)].x + p[0].x) / 2, (p[cycle(-1, p.length)].y + p[0].y) / 2);

            for (var i = 0; i < p.length; i++) {
                ctx.quadraticCurveTo(p[i].x, p[i].y, (p[i].x + p[cycle(i + 1, p.length)].x) / 2, (p[i].y + p[cycle(i + 1, p.length)].y) / 2);
            }

            ctx.closePath();

            ctx.globalCompositeOperation = 'lighter';
            ctx.stroke();
            ctx.globalCompositeOperation = 'source-over';
        };

        _this5.update = function (_ref6) {
            var tick = _ref6.tick;

            _this5.speed *= _this5.acceleration;
            // this.lineWidth *= (this.acceleration / 2);

            _this5.points = _this5.points.map(function (p, i) {
                var wobbleAmount = Math.sin(tick / 20 + i * _this5.radOff) * _this5.wobble;
                var warpAmount = Math.cos(tick / 100) * _this5.warp;

                var _movePointAtAngle = movePointAtAngle(p, p.radian, _this5.speed),
                    x = _movePointAtAngle.x,
                    y = _movePointAtAngle.y;

                return _extends({}, p, {
                    x: x + wobbleAmount - warpAmount,
                    y: y - wobbleAmount + warpAmount
                });
            });

            if (!_this5.dead) {
                --_this5.decay;
                if (_this5.decay === 0) {
                    _this5.dead = true;
                }
            }
        };

        _this5.points = [];

        _this5.w = _this5.toValue(window.innerWidth);
        _this5.h = _this5.toValue(window.innerHeight);
        _this5.hw = _this5.w / 2;
        _this5.hh = _this5.h / 2;
        _this5.speed = _this5.toValue(speed);
        _this5.decay = decay;
        _this5.acceleration = acceleration;
        _this5.lineWidth = _this5.toValue(1);
        _this5.warp = _this5.toValue(warp);
        _this5.wobble = _this5.toValue(wobble);
        _this5.radOff = 2 * Math.PI / pointAmount;

        _this5.opacityStroke = 1;
        _this5.opacityFill = 0;
        _this5.opacityDecay = 1 / _this5.decay;

        _this5.center = {
            x: _this5.hw,
            y: _this5.hh
        };

        for (var i = 0; i < pointAmount; i++) {
            var radian = Math.PI * 2 / pointAmount * i;
            var x = _this5.center.x + radius * Math.cos(radian);
            var y = _this5.center.y + radius * Math.sin(radian);

            _this5.points.push({
                x: x,
                y: y,
                radian: radian
            });
        }
        return _this5;
    }

    _createClass(Ring, [{
        key: 'stroke',
        get: function get() {
            this.opacityStroke -= this.opacityDecay;
            var r = Math.floor(170 + Math.sin(this.decay / 10) * 60);
            var g = Math.floor(130 + Math.sin(this.decay / 5) * 40);
            // const b = Math.floor(130 + (Math.sin(this.decay / 5) * 40));

            return 'rgba(' + r + ', ' + g + ', 200, ' + this.opacityStroke + ')';
        }
    }]);

    return Ring;
}(Element);

//*??????????????????????????????/
// Portal
//*?????????????????????????????*/

var Portal = function () {
    function Portal(time, ringConfig) {
        var _this6 = this;

        _classCallCheck(this, Portal);

        this.reset = function () {
            _this6.rings = [];
        };

        this.draw = function () {};

        this.update = function (_ref7) {
            var ctx = _ref7.ctx,
                tick = _ref7.tick;

            if (tick % _this6.time === 0) {
                _this6.addRing();
            }
            // filter dead while drawing and updating
            _this6.rings = _this6.rings.filter(function (ring) {
                ring.draw({ ctx: ctx, tick: tick });
                ring.update({ ctx: ctx, tick: tick });
                return ring.dead !== true;
            });
        };

        this.time = time;
        this.ringConfig = ringConfig;
        this.rings = [new Ring(this.ringConfig)];
    }

    _createClass(Portal, [{
        key: 'addRing',
        value: function addRing() {
            this.rings.push(new Ring(this.ringConfig));
        }
    }]);

    return Portal;
}();

//*??????????????????????????????/
// Background
//*?????????????????????????????*/

var Background = function (_Element4) {
    _inherits(Background, _Element4);

    function Background() {
        var _ref8;

        var _temp2, _this7, _ret;

        _classCallCheck(this, Background);

        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
        }

        return _ret = (_temp2 = (_this7 = _possibleConstructorReturn(this, (_ref8 = Background.__proto__ || Object.getPrototypeOf(Background)).call.apply(_ref8, [this].concat(args))), _this7), _this7.draw = function (_ref9) {
            var ctx = _ref9.ctx,
                canvas = _ref9.canvas;

            var w = canvas.width;
            var h = canvas.height;

            // const gradient1 = ctx.createLinearGradient(0, 0, 0, h);
            // gradient1.addColorStop(0, '#aa5eed');
            // gradient1.addColorStop(1, '#b7f7ed');
            // ctx.fillStyle = gradient1;
            // ctx.fillRect(0, 0, w, h);

            var gradient2 = ctx.createRadialGradient(w / 2, h / 2, 0, w / 2, h / 2, w);
            gradient2.addColorStop(1, '#20008c');
            gradient2.addColorStop(0, '#86efcc');
            ctx.fillStyle = gradient2;
            ctx.fillRect(0, 0, w, h);
            // ctx.globalCompositeOperation = 'source-over';
        }, _temp2), _possibleConstructorReturn(_this7, _ret);
    }

    return Background;
}(Element);

//*??????????????????????????????/
// Canvas
//*?????????????????????????????*/

var Canvas = function () {
    function Canvas() {
        var _this8 = this;

        var elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

        _classCallCheck(this, Canvas);

        this.setPointer = function (_ref10) {
            var clientX = _ref10.clientX,
                clientY = _ref10.clientY;

            var x = clientX;
            var y = clientY;
            _this8.mouse = { x: x, y: y };
        };

        this.setCanvasSize = function () {
            _this8.canvas.width = window.innerWidth * _this8.dpr;
            _this8.canvas.height = window.innerHeight * _this8.dpr;
            _this8.canvas.style.width = window.innerWidth + 'px';
            _this8.canvas.style.height = window.innerHeight + 'px';
        };

        this.render = function () {
            _this8.draw();
            _this8.update();
            ++_this8.tick;
            window.requestAnimationFrame(_this8.render);
        };

        // setup a canvas
        this.canvas = document.getElementById('canvas');
        this.dpr = 1;
        this.ctx = this.canvas.getContext('2d');
        this.ctx.scale(this.dpr, this.dpr);
        // stores
        this.elements = elements;
        this.tick = 0;
        this.mouse = { x: 0, y: 0 };
        // run
        this.setCanvasSize();
        this.setupListeners();
    }

    _createClass(Canvas, [{
        key: 'setupListeners',
        value: function setupListeners() {
            window.addEventListener('resize', this.setCanvasSize);
            window.addEventListener('mousemove', this.setPointer);
        }
    }, {
        key: 'addElement',
        value: function addElement(newElement) {
            this.elements = [].concat(_toConsumableArray(this.elements), [newElement]);
            return this.elements.length - 1;
        }
    }, {
        key: 'removeElement',
        value: function removeElement(deleteIndex) {
            this.elements = this.elements.filter(function (el, i) {
                return i !== deleteIndex;
            });
            return this.elements;
        }
    }, {
        key: 'update',
        value: function update() {
            var _this9 = this;

            this.elements.map(function (_ref11) {
                var update = _ref11.update;
                return update(_this9);
            });
        }
    }, {
        key: 'draw',
        value: function draw() {
            var _this10 = this;

            this.elements.map(function (_ref12) {
                var draw = _ref12.draw;
                return draw(_this10);
            });
        }
    }]);

    return Canvas;
}();

var canvas = new Canvas([new Background(), new Portal(5, {
    radius: 0,
    pointAmount: 3,
    speed: 2,
    decay: 200,
    acceleration: 1.02,
    wobble: 1,
    warp: 3
})]);

canvas.render();</script>

</body>
</html>
